package com.astersoft.lib.util.mail;

/*
 * Copyright 2012 AsterSoft personal developer - Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import java.io.File;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

/**
 * Utility class for sending mail.
 * 
 * You can easily send the content of a certain webpage, by doing: new DataHandler (new URL("http://www.thepage.com") ) or any file with the other
 * DataHandler constructors.
 * 
 * <p>
 * Interesting mail-related info at
 * <LI>http://www.faqs.org/rfcs/rfc2076.html
 * <LI>http://developer.java.sun.com/developer/onlineTraining/JavaMail/contents.html
 * <LI>http://www.jspinsider.com/tutorials/jsp/javamail/jspjavamail_intro.jsp
 * <LI>http://www.jspin.com/home/tutorials/email
 * 
 * <LI>http://java.sun.com/products/javamail/index.html
 * <LI>http://java.sun.com/products/javabeans/glasgow/javadocs/javax/activation/DataHandler.html
 * 
 * @version $Id: EmailSender.java,v 2.12 2010-09-29 11:29:16 rudi Exp $
 */
public class ASEmailSender {

	/** non-strict parsing of email addresses, recommended for human-input addresses. */
	static final boolean IA_PARSE_STRICT = false;

	static final String HOST = "centroitalia.coop.it"; // FIX? automatical like request.getAttribute("SERVER_NAME");

	static final String ERROR_FROM = "JMailUtil@" + HOST + " <no-reply@" + HOST + ">";

	static final String ERROR_TO = "dario.durzo@centroitalia.coop.it"; // send error message to developers

	static final String ERROR_SUBJECT = "[EMAILSENDER-ERROR] sending mail from " + HOST;

	static final boolean ERROR_SENDING = true;

	private boolean errorRptSending; //should attempt be made to send errorReport?

	private String errorRptFrom; //error report email FROM:

	private String errorRptTo; //error report email TO:

	private String errorRptSubject; //error report email SUBJECT:

	private String mailFromHost; //

	private InternetHeaders extraHeaders = new InternetHeaders();

	private Properties mailServerSettings;

	/** Indicate if Fail-Notification should be send if mail sending fails. */
	public void setErrorRptSending(boolean b) {
		this.errorRptSending = b;
	}

	/** Set From on Fail-Notification. */
	public void setErrorRptFrom(String s) {
		this.errorRptFrom = s;
	}

	/** Set To on Fail-Notification. */
	public void setErrorRptTo(String s) {
		this.errorRptTo = s;
	}

	/** Set Subject on Fail-Notification. */
	public void setErrorRptSubject(String s) {
		this.errorRptSubject = s;
	}

	/** Set From-Host for mails sent (=field in mail header). */
	public void setMailFromHost(String s) {
		this.mailFromHost = s;
	}

	/** Adds a new extra header */
	public void addHeader(String name, String value) {
		this.extraHeaders.addHeader(name, value);
	}

	/** Changes existing or adds a new extra header */
	public void setHeader(String name, String value) {
		this.extraHeaders.setHeader(name, value);
	}

	/** Remove all headers with the given name */
	public void removeHeader(String name) {
		this.extraHeaders.removeHeader(name);
	}

	public boolean getErrorRptSending() {
		return this.errorRptSending;
	}

	public String getErrorRptFrom() {
		return this.errorRptFrom;
	}

	public String getErrorRptTo() {
		return this.errorRptTo;
	}

	public String getErrorRptSubject() {
		return this.errorRptSubject;
	}

	public String getMailFromHost() {
		return this.mailFromHost;
	}


	/*************************************************************************************************************************************************
	 * CONSTRUCTORS
	 ************************************************************************************************************************************************/

	/** Default constructor : assumes hard-coded constants for parameters. */
	public ASEmailSender() {
		setErrorRptFrom(ERROR_FROM);
		setErrorRptTo(ERROR_TO);
		setErrorRptSubject(ERROR_SUBJECT);
		setMailFromHost(HOST);
		setErrorRptSending(ERROR_SENDING); // maybe this should be false?
	}

	public ASEmailSender(Properties mailServerSettings) {
		this();
		this.mailServerSettings = mailServerSettings;
	}

	/** constructor, setting some parameters. */
	public ASEmailSender(String _errFrom, String _errTo, String _errSubject) {
		setErrorRptFrom(_errFrom);
		setErrorRptTo(_errTo);
		setErrorRptSubject(_errSubject);
		setMailFromHost(HOST);
		setErrorRptSending(true);
	}

	/** constructor, setting some parameters. */
	public ASEmailSender(String _errFrom, String _errTo, String _errSubject, String _host) {
		setErrorRptFrom(_errFrom);
		setErrorRptTo(_errTo);
		setErrorRptSubject(_errSubject);
		setMailFromHost(_host);
		setErrorRptSending(true);
	}

	/***********************************************************************************************
	 * EMAIL SEND FUNCTIONS
	 **********************************************************************************************/

	public void send(String from, String to, String subject, String body, boolean isHTML) {
		send(from, to, "", "", "", subject, body, (DataHandler[]) null, isHTML);
	}
	
	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body, boolean isHTML) {
		send(from, to, cc, bcc, replyTo, subject, body, (DataHandler[]) null, isHTML);
	}
	
	public void send(String from, String to, String subject, String body, File[] attachments, boolean isHTML) {
		send(from, to, "", "", "", subject, body, attachments, isHTML);
	}
	
	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body, File[] attachments, boolean isHTML) {
		DataHandler[] dhs = null;
		if(attachments != null) {
			dhs = new DataHandler[attachments.length];
			int i = 0;
			for(File att: attachments){
				dhs[i++] = new DataHandler(new FileDataSource(att){
					@Override
					public String getContentType() {
						return "application/octet-stream";
					}
				});
			}
		}
		
		send(from, to, cc, bcc, replyTo, subject, body, dhs, isHTML);
	}
	
	/**
	 * Sends mail, convenient shorthand.
	 * 
	 * @see send(String, String, String, String, String, String, String)
	 */
	public void send(String from, String to, String subject, String body) {
		send(from, to, "", "", "", subject, body);
	}


	/**
	 * Sends mail with attachments, convenient shorthand.
	 * 
	 * @see send(String, String, String, String, String, String, String, DataHandler[] )
	 */
	public void send(String from, String to, String subject, String body, DataHandler[] attachments) {
		send(from, to, "", "", "", subject, body, attachments);
	}

	/**
	 * Sends mail, convenient shorthand.
	 * 
	 * @see send(String, String, String, String, String, String, String)
	 */
	public void send(String from, String to, String cc, String subject, String body) {
		send(from, to, cc, "", "", subject, body);
	}


	/**
	 * Sends mail without attachment, full version.
	 * 
	 * @see send(String, String, String, String)
	 * @see send(String, String, String, String, String)
	 */
	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body) {
		send(from, to, cc, bcc, replyTo, subject, body, (DataHandler[]) null);
	}


	/**
	 * Sends mail URL content as attachment.
	 * 
	 * @see send(String, String, String, String, String, String, String, DataHandler[])
	 */
	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body, URL url) {
		DataHandler dh = new DataHandler(url);
		DataHandler[] attachments = { dh };
		send(from, to, cc, bcc, replyTo, subject, body, attachments);
	}


	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body,
			DataHandler[] attachments) {
		send(from, to, cc, bcc, replyTo, subject, body, attachments, false);    
	}


	/**
	 * Sends mail with or without attachments, full version.
	 * 
	 * <p>
	 * Tries to send message to recipients, and if failure is detected on sending, it reports it to the outstream, plus tries to warn designated
	 * person(s) by sending Fail-Notification mail.
	 * 
	 * @see send(String, String, String, String, String)
	 * @see send(String, String, String, String, String)
	 */
	public void send(String from, String to, String cc, String bcc, String replyTo, String subject, String body,
			DataHandler[] attachments, boolean isHTMLmail) {
		String exceptionMsg;

		try {
			if (attachments == null) {
				sendAttempt(from, to, cc, bcc, replyTo, subject, body, isHTMLmail);
			} else {
				sendAttempt(from, to, cc, bcc, replyTo, subject, body, attachments, isHTMLmail);
			}
		} catch (MessagingException mexc) {
			System.out.println("-- ERROR in EmailSender.send()");
			mexc.printStackTrace(System.out);
			exceptionMsg = mexc.getMessage();

			String failureReport = "EmailSender failed to send mail! \n" + "\nTO: " + to + "\nCC: "
					+ cc + "\nBCC: " + bcc + "\nReply-To: " + replyTo + "\nSubject: " + subject
					+ (attachments == null ? "" : ("\nNr of Attachments: " + attachments.length)) + "\nBody:\n" + body;
			System.out.println(failureReport);

			if (errorRptSending) {
				try {
					sendAttempt(errorRptFrom, errorRptTo, "", "", "", errorRptSubject, failureReport + "\n--\n--\n\n"
							+ exceptionMsg);
					System.out.println("-- ERROR in EmailSender.send() - Fail-Notification sent to " + errorRptTo);
				} catch (MessagingException mexc2) {
					System.out.println("-- ERROR in EmailSender.send() - Fail-Notification NOT sent");
					mexc2.printStackTrace(System.out);
				} // end catch
			} else {
				System.out.println("-- ERROR in EmailSender.send() - NO Fail-Notification email requested");
			}//end if-else
		}
	} // end function


	/** Private : send mail without trying to track errors. */

	private void sendAttempt(String _from, String _to, String _cc, String _bcc, String _replyTo, String _subject,
			String _body) throws MessagingException {
		sendAttempt(_from, _to, _cc, _bcc, _replyTo, _subject, _body, false);
	}

	private void sendAttempt(String _from, String _to, String _cc, String _bcc, String _replyTo, String _subject,
			String _body, boolean isHTMLmail) throws MessagingException {

		if (mailServerSettings == null) {
			mailServerSettings = new Properties();
			mailServerSettings.put("mail.smtp.host", "localhost");
		}
		Session s = Session.getInstance(mailServerSettings, null);

		MimeMessage message = new MimeMessage(s);

		InternetAddress from = new InternetAddress(_from);
		message.setFrom(from);

		//SINGLE recipient:
		//InternetAddress to = new InternetAddress(_to);
		//message.addRecipient(Message.RecipientType.TO, to);

		//MULTIPLE recipient (comma-separated list):
		InternetAddress[] to = InternetAddress.parse(_to, IA_PARSE_STRICT);
		InternetAddress[] cc = InternetAddress.parse(_cc, IA_PARSE_STRICT);
		InternetAddress[] bcc = InternetAddress.parse(_bcc, IA_PARSE_STRICT);
		InternetAddress[] replyTo = InternetAddress.parse(_replyTo, IA_PARSE_STRICT);
		message.addRecipients(Message.RecipientType.TO, to);
		message.addRecipients(Message.RecipientType.CC, cc);
		message.addRecipients(Message.RecipientType.BCC, bcc);
		message.setReplyTo(replyTo);
		message.setHeader("X-Mailer", mailFromHost + " using JavaMail API");

		for (@SuppressWarnings("unchecked")
		Enumeration<Header> e = extraHeaders.getAllHeaders() ; e.hasMoreElements() ;) {
			Header h = e.nextElement();
			message.addHeader(h.getName(), h.getValue());
		}
		message.setSubject(_subject);
		message.setText(_body);
		if (isHTMLmail) {
			message.setContent(_body, "text/html;charset=\"UTF-8\"");
		} else {
			message.setContent(_body, "text/plain;charset=\"UTF-8\"");
		}

		Transport.send(message);

	} //end function

	
	/**
	 * Private : send mail with attachments, without trying to track errors.
	 */

	private void sendAttempt(String _from, String _to, String _cc, String _bcc, String _replyTo, String _subject,
			String _body, DataHandler[] attachments, boolean isHTMLmail) throws MessagingException {

		if (mailServerSettings == null) {
			mailServerSettings = new Properties();
			mailServerSettings.put("mail.smtp.host", "localhost");
		}
		Session s = Session.getInstance(mailServerSettings, null);

		MimeMessage message = new MimeMessage(s);

		InternetAddress from = new InternetAddress(_from);
		message.setFrom(from);

		//SINGLE recipient:
		//InternetAddress to = new InternetAddress(_to);
		//message.addRecipient(Message.RecipientType.TO, to);

		//MULTIPLE recipient (comma-separated list):
		InternetAddress[] to = InternetAddress.parse(_to, IA_PARSE_STRICT);
		InternetAddress[] cc = InternetAddress.parse(_cc, IA_PARSE_STRICT);
		InternetAddress[] bcc = InternetAddress.parse(_bcc, IA_PARSE_STRICT);
		InternetAddress[] replyTo = InternetAddress.parse(_replyTo, IA_PARSE_STRICT);
		message.addRecipients(Message.RecipientType.TO, to);
		message.addRecipients(Message.RecipientType.CC, cc);
		message.addRecipients(Message.RecipientType.BCC, bcc);
		message.setReplyTo(replyTo);
		message.setHeader("X-Mailer", mailFromHost + " using JavaMail API");

		for (@SuppressWarnings("unchecked")
		Enumeration<Header> e = extraHeaders.getAllHeaders() ; e.hasMoreElements() ;) {
			Header h = e.nextElement();
			message.addHeader(h.getName(), h.getValue());
		}

		message.setSubject(_subject);

		// Container for all parts
		Multipart multipart = new MimeMultipart();

		// Create the message part, fill it and add it to multipart container
		BodyPart messageBodyPart = new MimeBodyPart();
		messageBodyPart.setText(_body);
		if (isHTMLmail) {
			message.setContent(_body, "text/html;charset=\"UTF-8\"");
		} else {
			message.setContent(_body, "text/plain;charset=\"UTF-8\"");
		}
		multipart.addBodyPart(messageBodyPart);

		// Part 2-N are attachments
		if(attachments != null) {
			for (DataHandler attachment : attachments) {
				messageBodyPart = new MimeBodyPart();
				messageBodyPart.setDataHandler(attachment);
				messageBodyPart.setFileName(attachment.getDataSource().getName());
				multipart.addBodyPart(messageBodyPart);
			}
		}

		// Put parts in message
		message.setContent(multipart);

		Transport.send(message);

	} //end function

	/*************************************************************************************************************************************************
	 * UTILITY FUNCTIONS RELATED TO MAIL SENDING
	 ************************************************************************************************************************************************/

	/**
	 * Validates correctness of email address.
	 * <p>
	 * Valid email addresses are
	 * <li>test@testing.tld, test.tester@alfa.beta.com, etc. (exactly one @ and at least one dot after it)
	 * <li>(name othernames) test@testing.tld (no @ allowed in the name)
	 * <li>"name othernames" &lt;test@testing.tld&gt; (no @ allowed in the name) <br>
	 * anything else will likely be refused, although the javamail is excepting some other things too. At least then they should not be giving any
	 * "malformed address" exceptions
	 * 
	 * @param email
	 *            <code>String</code> that represents the email address to test
	 * @return <code>true</code> if the email is valid according to RFC 822
	 * @see InternetAddress
	 * @see #isValidEmailAddressList(String)
	 */
	public static boolean isValidEmailAddress(String email) {
		if (email == null) {
			return false;
		}

		// check there is exactly one @
		int atPos = email.indexOf('@');
		if (atPos == -1) {
			return false;
		}
		if (atPos != email.lastIndexOf('@')) {
			return false;
		}

		// check that there is at least one dot after the @
		if (email.indexOf('.', atPos) == -1) {
			return false;
		}

		try {
			InternetAddress.parse(email, true);
		} catch (Throwable ae) {
			return false;
		}
		return true;
	} // 

	/**
	 * Validates correctness of email addresses in a comma-separated list.
	 * <p>
	 * Valid email address LISTs are lists of comma separated VALID email addresses. An empty element (a, ,b, c) is allowed, as well as a trailing
	 * comma (a, b,).
	 * 
	 * @param emailList
	 *            <code>String</code> that represents the comma-separated list of email addresses to test
	 * @return <code>true</code> if the email addresses are all valid according to RFC 822
	 * @see InternetAddress
	 * @see #isValidEmailAddress(String)
	 */
	public static boolean isValidEmailAddressList(String emailList) {
		StringTokenizer st = new StringTokenizer(emailList, ",");
		while (st.hasMoreTokens()) {
			String email = st.nextToken();
			// fred: let empty list element pass
			if (email.trim().length() > 0) {
				if (!isValidEmailAddress(email)) {
					return false;
				}
			}
		}
		try {
			InternetAddress.parse(emailList, false);
		} catch (Throwable ae) {
			return false;
		}
		return true;
	} //end method

	/**
	 * Erases the invalid and duplicated email addresses from a comma separated list.
	 * <p>
	 * Takes a comma-separated list of email addresses, and returns another comma separated list of only the valid email addresses, with no
	 * duplicates.
	 * 
	 * @param emailList
	 *            <code>String</code> that represents the comma-separated list of email addresses to test
	 * @return <code>String</code> copy of emailList but with all invalid email addresses erased
	 * @see InternetAddress
	 * @see #isValidEmailAddress(String)
	 * @see #isValidEmailAddressList(String)
	 */
	public static String trimValidEmailAddressList(String emailList) {
		StringBuffer newList = new StringBuffer(emailList.length());
		StringTokenizer st = new StringTokenizer(emailList, ",");
		Vector<String> mails = new Vector<String>();
		while (st.hasMoreTokens()) {
			String email = st.nextToken().trim();
			if (!email.equals("") && isValidEmailAddress(email) && !mails.contains(email.toLowerCase())) {
				newList.append(email).append(", ");
				mails.add(email.toLowerCase());
			}
		}

		int len = newList.length();
		if (len > 0) {
			newList.delete(len - 2, len); // remove last ", " that was added
		}
		return newList.toString();

	} //end method

	/**
	 * Main entry test point
	 * @param args
	 */
	public static void main(String[] args) {
		String host = "smtp.centroitalia.coop.it";

		Properties props = new Properties();
		props.put("mail.smtp.host", host);
		props.put("mail.smtp.auth", "false");
		
		/*
		ASEmailSender aes = new ASEmailSender(props);
		File[] atfiles = new File[2];
		atfiles[0] = new File("C:\\TEMP\\log.txt");
		atfiles[1] = new File("C:\\TEMP\\log2.txt");
		
		aes.send("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova", "Ciao!", atfiles, true);
		*/
		
		ASEmailSenderThread th = new ASEmailSenderThread(props);
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova1", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova2", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova3", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova4", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova5", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova6", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova7", "ciao!"));
		th.addEmail(new ASEmail("pippo@centroitalia.coop.it", "dario.durzo@centroitalia.coop.it", "prova8", "ciao!"));
		
		Thread t = new Thread(th);
		t.start();
	}
} //end class

